// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LiveObjectsRegistry.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.LiveObjects
{
    #region Using Directives

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;

    using Org.OpenTrader.Framework.Entities;
    using Org.OpenTrader.Framework.Enums;
    using Org.OpenTrader.Framework.Events;
    using Org.OpenTrader.Framework.LiveObjects.Interfaces;

    #endregion

    /// <summary>
    /// The global gate for Local and Remotable LiveObjects
    /// </summary>
    public class LiveObjectsRegistry : RemotingStub, ILiveObjectsRegistry, IDisposable
    {
        #region Constants and Fields

        /// <summary>
        /// The identity.
        /// </summary>
        private static readonly Identity Identity = Identity.Create(System.Reflection.MethodBase.GetCurrentMethod());

        /// <summary>
        /// All containers
        /// </summary>
        private readonly IDictionary<Guid, ILiveObjectContainerManager> containerManagers = new Dictionary<Guid, ILiveObjectContainerManager>();

        /// <summary>
        /// All stubs
        /// </summary>
        private readonly IDictionary<Guid, StubInfo> stubsInfo = new Dictionary<Guid, StubInfo>();

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="LiveObjectsRegistry"/> class.
        /// </summary>
        public LiveObjectsRegistry()
        {
            this.LiveObjectsEventCB = new RemotableEventHandler<LiveObjectsEventArgs>("LiveObjects");
        }

        #endregion

        // Events

        // public delegate void ExistingContainerAction(ILiveObjectContainer c);
        // private void EnsureContainerExistsAndExecute(Guid containerId, ExistingContainerAction action)
        // {
        // if (!ExistsContainer(containerId))
        // CreateContainer(containerId, "" + Timings.Now.Ticks, DBContainerHelpers.GetResources(containerId),false);

        // action(GetContainer(containerId));
        // }
        #region Properties

        /// <summary>
        /// Gets Containers.
        /// </summary>
        public Guid[] Containers
        {
            get
            {
                return this.containerManagers.Keys.ToArray();
            }
        }

        /// <summary>
        /// Gets CurrentLiveObjectContainer.
        /// </summary>
        public ILiveObjectContainer CurrentLiveObjectContainer { get; set; }

        /// <summary>
        /// Gets or sets LiveObjectsEventCB.
        /// </summary>
        public RemotableEventHandler<LiveObjectsEventArgs> LiveObjectsEventCB { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Activate this application
        /// </summary>
        /// <param name="dbApplication">
        /// The dbApplication.
        /// </param>
        /// <param name="executionContextName">
        /// The executionContextName.
        /// </param>
        public IApplication CreateLocalApplication(DBLiveObject dbApplication, string executionContextName)
        {
            Logging.Log(
                Identity, 
                ELogLevel.Info, 
                () => string.Format("CreateLocalApplication: {0} ({1}) ExecutionContext={2}", dbApplication.Name, dbApplication.ID, executionContextName));

            this.LiveObjectsEventCB.RaiseEvent(this, new LiveObjectsEventArgs("Creating Application " + executionContextName));

            // Locator of application
            var applicationInstanceDescriptor = DBLiveObjectHelpers.CreateDescriptor(dbApplication, Guid.Empty) as ILocalLiveObjectDescriptor;

            // Container to run
            var applicationContainer = Singleton<LiveObjectsRegistry>.Instance.GetContainer(dbApplication.DBContainer.ID);

            // Create application
            var application = applicationContainer.CreateLiveObject<Application>(applicationInstanceDescriptor);

            // Error in creation
            if (application == null)
            {
                Logging.Log(
                    Identity, 
                    ELogLevel.Error, 
                    () => string.Format("CreateLocalApplication: {0} ({1}) failed to create application", dbApplication.Name, dbApplication.ID));
                throw new Exception(string.Format("Unable to create application {0}", applicationInstanceDescriptor));
            }

            application.ContextName = executionContextName;

            // Application configuration
            application.Configuration = DBConfigurationHelpers.LoadConfiguration(
                applicationInstanceDescriptor.ConfigurationID, dbApplication.ClassFullTypeName, applicationContainer);

            // To registry
            this.Register(application, null);

            // All services, in the required instantiation order    
            IDictionary<string, IService> services = new Dictionary<string, IService>();
            dbApplication.DBLiveObject1.Load();
            var dbApplicationServices = from s in dbApplication.DBLiveObject1 select s;

            foreach (var dbApplicationService in dbApplicationServices)
            {
                // Service instanceDescriptor
                var serviceInstanceDescriptor = DBLiveObjectHelpers.CreateDescriptor(dbApplicationService, dbApplication.ID);

                // service
                var service = this.CreateLocalService(dbApplicationService, application);

                // Set to application
                application.AddService(serviceInstanceDescriptor.Name, service);
            }

            this.LiveObjectsEventCB.RaiseEvent(this, new LiveObjectsEventArgs("Created Application " + executionContextName));

            Logging.Log(Identity, ELogLevel.Info, () => string.Format("CreateLocalApplication: {0} ({1}) success", dbApplication.Name, dbApplication.ID));

            return application;
        }

        /// <summary>
        /// The create local plugin.
        /// </summary>
        /// <param name="dbPlugin">
        /// The dbPlugin.
        /// </param>
        /// <param name="service">
        /// The service.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="Exception">
        /// </exception>
        public IPlugin CreateLocalPlugin(DBLiveObject dbPlugin, IService service)
        {
            Logging.Log(
                Identity, ELogLevel.Info, () => string.Format("CreateLocalPlugin: {0} ({1}) Service={2}", dbPlugin.Name, dbPlugin.ID, service.Descriptor.ID));

            this.LiveObjectsEventCB.RaiseEvent(this, new LiveObjectsEventArgs("Creating Plugin " + dbPlugin.Name));

            // Create plugin instance
            var pluginInstanceDescriptor = DBLiveObjectHelpers.CreateDescriptor(dbPlugin, service.Descriptor.ID) as ILocalLiveObjectDescriptor;

            // Container to run
            var pluginContainer = Singleton<LiveObjectsRegistry>.Instance.GetContainer(dbPlugin.DBContainer.ID);

            // Plugin
            var plugin = pluginContainer.CreateLiveObject<Plugin>(pluginInstanceDescriptor);

            // Error in creation
            if (plugin == null)
            {
                throw new Exception(String.Format("Unable to create plugin {0}", pluginInstanceDescriptor));
            }

            // Plugin Configuration
            plugin.Configuration = DBConfigurationHelpers.LoadConfiguration(
                pluginInstanceDescriptor.ConfigurationID, dbPlugin.ClassFullTypeName, pluginContainer);

            // To registry
            this.Register(plugin, service);

            this.LiveObjectsEventCB.RaiseEvent(this, new LiveObjectsEventArgs("Created Plugin " + dbPlugin.Name));

            Logging.Log(Identity, ELogLevel.Info, () => string.Format("CreateLocalPlugin: {0} ({1}) success", dbPlugin.Name, dbPlugin.ID));

            // All done
            return plugin;
        }

        /// <summary>
        /// The create local service.
        /// </summary>
        /// <param name="dbService">
        /// The dbService.
        /// </param>
        /// <param name="application">
        /// The application.
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="Exception">
        /// </exception>
        public IService CreateLocalService(DBLiveObject dbService, IApplication application)
        {
            Logging.Log(
                Identity, 
                ELogLevel.Info, 
                () => string.Format("CreateLocalService: {0} ({1}) Application={2}", dbService.Name, dbService.ID, application.Descriptor.ID));

            this.LiveObjectsEventCB.RaiseEvent(this, new LiveObjectsEventArgs("Creating Service " + dbService.Name));

            // Service instanceDescriptor
            var serviceInstanceDescriptor = DBLiveObjectHelpers.CreateDescriptor(dbService, application.Descriptor.ID) as ILocalLiveObjectDescriptor;

            // Container to run
            var serviceContainer = Singleton<LiveObjectsRegistry>.Instance.GetContainer(dbService.DBContainer.ID);

            // Create service
            var service = serviceContainer.CreateLiveObject<Service>(serviceInstanceDescriptor);

            // Error in creation
            if (service == null)
            {
                throw new Exception(String.Format("Unable to create service {0}", serviceInstanceDescriptor));
            }

            // Service Configuration
            service.Configuration = DBConfigurationHelpers.LoadConfiguration(
                serviceInstanceDescriptor.ConfigurationID, dbService.ClassFullTypeName, serviceContainer);

            // To registry
            this.Register(service, application);

            // All plugins in this service
            dbService.DBLiveObject1.Load();
            foreach (var dbServicePlugin in dbService.DBLiveObject1)
            {
                // Create plugin instance
                var plugin = this.CreateLocalPlugin(dbServicePlugin, service);

                // Add the plugin
                service.AddPlugin(plugin);
            }

            this.LiveObjectsEventCB.RaiseEvent(this, new LiveObjectsEventArgs("Created Service " + dbService.Name));

            Logging.Log(Identity, ELogLevel.Info, () => string.Format("CreateLocalService: {0} ({1}) success", dbService.Name, dbService.ID));

            // All done
            return service;
        }

        #endregion

        #region Implemented Interfaces

        #region IDisposable

        /// <summary>
        /// The dispose.
        /// </summary>
        public void Dispose()
        {
        }

        #endregion

        #region ILiveObjectsRegistry

        /// <summary>
        /// Creates the container.
        /// </summary>
        /// <param name="desc">
        /// The desc.
        /// </param>
        /// <returns>
        /// The create container.
        /// </returns>
        public bool CreateContainer(ILiveObjectContainerDescriptor desc)
        {
            Logging.Log(Identity, ELogLevel.Info, () => string.Format("CreateContainer: {0} ({1}) Type={2}", desc.Name, desc.ID, desc.Type));

            // Can only create from default appdomain
            if (!AppDomain.CurrentDomain.IsDefaultAppDomain())
            {
                Logging.Log(
                    Identity, 
                    ELogLevel.Error, 
                    () => string.Format("CreateContainer: {0} ({1}) cannot be created, only default AppDomain may create containers", desc.Name, desc.ID));
                return false;
            }

            // Can only create if not already created
            if (this.containerManagers.ContainsKey(desc.ID))
            {
                Logging.Log(Identity, ELogLevel.Error, () => string.Format("CreateContainer: {0} ({1}) already exists", desc.Name, desc.ID));
                return false;
            }

            ILiveObjectContainerManager manager = null;
            switch (desc.Type)
            {
                case ELiveObjectContainerType.DefaultContainer:
                    Logging.Log(
                        Identity, ELogLevel.Info, () => string.Format("CreateContainer: {0} ({1}) using LiveObjectContainerManagerDefault", desc.Name, desc.ID));
                    manager = new LiveObjectContainerManagerDefault(desc);
                    break;

                case ELiveObjectContainerType.ProcessIsolationContainer:
                    Logging.Log(
                        Identity, 
                        ELogLevel.Info, 
                        () => string.Format("CreateContainer: {0} ({1}) using LiveObjectContainerManagerExternalProcess", desc.Name, desc.ID));
                    manager = new LiveObjectContainerManagerExternalProcess(desc);
                    break;

                case ELiveObjectContainerType.AppDomainIsolationContainer:
                    Logging.Log(
                        Identity, 
                        ELogLevel.Info, 
                        () => string.Format("CreateContainer: {0} ({1}) using LiveObjectContainerManagerInternalAppDomain", desc.Name, desc.ID));
                    manager = new LiveObjectContainerManagerInternalAppDomain(desc);
                    break;

                default:
                    return false;
            }

            // Deploy needed resources
            Directory.CreateDirectory(desc.WorkingDirectory);
            var resources = this.DeployResources(desc.WorkingDirectory, desc.Resources);
            Logging.Log(
                Identity, 
                ELogLevel.Info, 
                () => string.Format("CreateContainer: {0} ({1}) deployed resources : {2}", desc.Name, desc.ID, string.Join(",", resources)));

            try
            {
                // Create the 
                if (manager.CreateContainer())
                {
                    // Chain Events (events from are forwarded to here)
                    manager.Container.LiveObjectsEventCB.Chain(this.LiveObjectsEventCB);

                    // Store manager into registry
                    this.containerManagers[desc.ID] = manager;
#if BENCHMARKS
    


    // Benchmark container
                    manager.BenchmarkContainer();
#endif

                    Logging.Log(Identity, ELogLevel.Info, () => string.Format("CreateContainer: {0} ({1}) created successfully", desc.Name, desc.ID));

                    // All went good
                    return true;
                }
            }
            catch (Exception ex)
            {
                Logging.Log(
                    Identity, ELogLevel.Error, () => string.Format("CreateContainer: {0} ({1}) failed to create, got exception", desc.Name, desc.ID), ex);
                return false;
            }

            // Release resources
            foreach (var c in desc.Resources)
            {
                c.Content = null;
            }

            // Error somewhere
            return false;
        }

        /// <summary>
        /// The destroy container.
        /// </summary>
        /// <param name="containerId">
        /// The containerId.
        /// </param>
        public void DestroyContainer(Guid containerId)
        {
            // Not known, nothing to destroy then !
            if (!this.containerManagers.ContainsKey(containerId))
            {
                return;
            }

            // This is the manager related to this container
            var manager = this.containerManagers[containerId];

            // UnChain Events (events from are no more forwarded to here)
            manager.Container.LiveObjectsEventCB.UnChain(this.LiveObjectsEventCB);

            // Remove stubs of objects running inside this from registry
            var removed = from k in this.stubsInfo where k.Value.Stub.Container.Descriptor.ID == containerId select k;
            var removeds = removed.ToArray();
            foreach (var remove in removeds)
            {
                this.stubsInfo.Remove(remove.Key);
            }

            // Ask manager to destroy the container
            manager.DestroyContainer();

            // Remove manager from map
            this.containerManagers.Remove(containerId);

            // Delete the SandBox directory
            Directory.Delete(manager.Descriptor.WorkingDirectory, true);
        }

        /// <summary>
        /// The exists container.
        /// </summary>
        /// <param name="containerId">
        /// The containerId.
        /// </param>
        /// <returns>
        /// The exists container.
        /// </returns>
        public bool ExistsContainer(Guid containerId)
        {
            return this.containerManagers.ContainsKey(containerId);
        }

        /// <summary>
        /// Gets the container.
        /// </summary>
        /// <param name="containerId">
        /// The containerId.
        /// </param>
        /// <returns>
        /// </returns>
        public ILiveObjectContainer GetContainer(Guid containerId)
        {
            if (this.containerManagers.ContainsKey(containerId))
            {
                return this.containerManagers[containerId].Container;
            }

            return null;
        }

        /// <summary>
        /// Get the live object with this unique ID if exists
        /// </summary>
        /// <param name="objectId">
        /// </param>
        /// <returns>
        /// </returns>
        public ILiveObject GetLiveObject(Guid objectId)
        {
            if (this.stubsInfo.ContainsKey(objectId))
            {
                return this.stubsInfo[objectId].Stub;
            }

            return null;
        }

        /// <summary>
        /// Gets the live object with a special label
        /// </summary>
        /// <param name="label"></param>
        /// <returns></returns>
        public ILiveObject GetLiveObject(string label)
        {
            var lobjs = from lo in stubsInfo where lo.Value.Stub.Descriptor.Label == label select lo;
        
            if( lobjs.Count() == 1 )
            {
                return lobjs.First().Value.Stub;
            }

            return null;
        }


        /// <summary>
        /// Get the parent of this object if exists
        /// </summary>
        /// <param name="objectId">
        /// </param>
        /// <returns>
        /// </returns>
        public ILiveObject GetLiveObjectParent(Guid objectId)
        {
            if (this.stubsInfo.ContainsKey(objectId))
            {
                return this.stubsInfo[objectId].ParentStub;
            }

            return null;
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The deploy resources.
        /// </summary>
        /// <param name="dir">
        /// The dir.
        /// </param>
        /// <param name="resources">
        /// The resources.
        /// </param>
        /// <returns>
        /// </returns>
        private string[] DeployResources(string dir, IResource[] resources)
        {
            // Create resources in the Sand Boxed directory, keeping assembly names for domaiin setup
            IList<string> assemblyNames = new List<string>();
            foreach (var resource in resources)
            {
                var name = resource.Name.Replace(resource.Extension, string.Empty);

                // Deploy everything hack 
                // goto deploy;

                // Keep the bootstrap assembly
                if (name.EndsWith("Framework"))
                {
                    goto deploy;
                }

                // Keep all assemblyes that are not marked "code assembly", thus dependancies
                if (! resource.IsAssembly && ! resource.Extension.Equals(".pdb"))
                {
                    goto deploy;
                }

                // Don't deploy current resource, jump to next
                goto dontDeploy;

                dontDeploy:
                continue; // go to next resource immediately

                deploy:
                this.LiveObjectsEventCB.RaiseEvent(this, new LiveObjectsEventArgs("Deploying resource " + resource.Name));
                var fileName = string.Format("{0}\\{1}", dir, resource.Name);
                using (var fs = File.Create(fileName))
                {
                    fs.Write(resource.Content, 0, resource.Content.Length);
                    fs.Close();

                    // If this holds code
                    if (resource.IsAssembly)
                    {
                        var assemblyName = resource.Name.Replace(resource.Extension, string.Empty);
                        assemblyNames.Add(assemblyName);
                    }

                    // resource.Content = null;
                }
            }

            return assemblyNames.ToArray();
        }

        /// <summary>
        /// The register.
        /// </summary>
        /// <param name="liveObject">
        /// The liveObject.
        /// </param>
        /// <param name="parentLiveObject">
        /// The parentLiveObject.
        /// </param>
        private void Register(ILiveObject liveObject, ILiveObject parentLiveObject)
        {
            var info = new StubInfo();

            info.Stub = liveObject;
            info.ParentStub = parentLiveObject;

            this.stubsInfo[liveObject.Descriptor.ID] = info;
        }

        #endregion

        /// <summary>
        /// The stub info.
        /// </summary>
        internal class StubInfo
        {
            #region Properties

            /// <summary>
            /// Gets or sets ParentStub.
            /// </summary>
            public ILiveObject ParentStub { get; set; }

            /// <summary>
            /// Gets or sets Stub.
            /// </summary>
            public ILiveObject Stub { get; set; }

            #endregion
        }
    }
}